/*
 *    This file is part of BlobDB.
 *
 *    BlobDB is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU General Public License as published by
 *    the Free Software Foundation, either version 3 of the License, or
 *    (at your option) any later version.
 *
 *    BlobDB is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU General Public License for more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with BlobDB.  If not, see <http://www.gnu.org/licenses/>.
 *    
 *    Copyright(c) 2011 Michael Kankkonen
 *    http://www.vnetcon.org
 */


package org.vnetcon.blobdb.database.util.parser;

import java.util.ArrayList;
import java.util.List;

import org.vnetcon.blobdb.database.SQLException;
import org.vnetcon.blobdb.database.util.SQLParser;

public class SelectTableParser {

	private ArrayList<String> elements = null;
	private ArrayList<String> columns = new ArrayList<String>();
	private ArrayList<String> subqueries = new ArrayList<String>();
	private ArrayList<String> whereSubqueries = new ArrayList<String>();
	private ArrayList<String[]> orderBy = new ArrayList<String[]>();

	private String table = null;
	private String wherePart = null;
	private String rowid = null;
	
	private int rangeStart = -1;
	private int rangeEnd = -1;

	public SelectTableParser(ArrayList<String> elements) throws SQLException{
		this.elements = elements;
		init();
	}

	public int getRangeStart(){
		return this.rangeStart;
	}
	
	public int getRangeEnd(){
		return this.rangeEnd;
	}
	
	private int parseSub(int i) throws SQLException {
		int iRet = i;
		String strSub = "";
		String strBuf = "";
		iRet++;
		while(iRet < elements.size()){
			strBuf = elements.get(iRet);
			if(strBuf.toUpperCase().equals("END")){
				iRet++;
				break;
			}else{
				strSub += " " + strBuf;
				iRet++;
			}
		}
		strSub += ";";
		this.subqueries.add(strSub);
		return iRet;
	}
	
	private void init() throws SQLException {
		int i = 0;
		boolean bInCols = true;
		boolean bInWhere = false;
		
		if(!elements.get(i).toUpperCase().equals("SELECT")){
			throw new SQLException("Statement dont start with 'SELECT'");
		}

		i++;
		while(i < elements.size()){
			String buf = elements.get(i);
			
			if(buf.toUpperCase().equals("SUB")){
				i = parseSub(i);
			} else if(buf.toUpperCase().equals("RANGE")){
				i = parseRange(i);
			}else{
				columns.add(buf);
				i++;
			}
			buf = elements.get(i);
			
			
			if(!buf.equals(",") && bInCols){
				if(!buf.toUpperCase().equals("FROM")){
					throw new SQLException("Expecting 'FROM'. Found " + buf);
				}else{
					i++;
					this.table = elements.get(i);
					if(table.equals("ROWID")){
						i++;
						buf = elements.get(i);
						if(!buf.equals("=")){
							throw new SQLException("Expected '=' after 'ROWID'. Found: " + buf);
						}
						i++;
						buf = elements.get(i);
						if(buf.endsWith("'") && buf.startsWith("'")){
							buf = buf.substring(1, buf.length() - 1);
						}
						this.rowid = buf;
						return;
					}
					i++;
					if(i <= elements.size() - 1){
						buf = elements.get(i);
						if(!buf.toUpperCase().equals("WHERE") && !buf.toUpperCase().equals(";")){
							throw new SQLException("Expecting 'WHERE' or ';'. Found " + buf);
						}
						
						if(i == elements.size() - 1 && !buf.equals(";")){
							throw new SQLException("Expecting ';'. Found " + buf);
						}

						if(i == elements.size() - 1 && buf.equals(";")){
							break;
						}

						if(buf.toUpperCase().equals("WHERE")){
							parseWhere(i);
							break;
						}
						
					}else{
						throw new SQLException("Expecting ';'. Found " + buf);
					}
					continue;
				}
			}
			
//			System.out.println(" select element: " + elements.get(i));
			i++;
		}
		
	}

	private void parseWhere(int i) throws SQLException{
		i++;
		wherePart = "";
		
		if(!elements.get(elements.size() - 1).equals(";")){
			throw new SQLException("Expecting ';' mark at the end of sql statement. Found: " + elements.get(elements.size() - 1));
		}
		
		while(i < elements.size() - 1){
			String part = elements.get(i);
			
			if(part.startsWith("'") && part.endsWith("'")){
				part = "\"" + part.substring(1, part.length() - 1) + "\"";
			}

			if(part.toUpperCase().equals("ORDER") && elements.get(i + 1).toUpperCase().equals("BY")){
				i++;
				i++;
				i = this.parserOrderBy(i);
				continue;
			}
			
			wherePart += part + " ";
			i++;
		}
	}

	private int parseRange(int i) throws SQLException{
		int iRet = i;
//		while(iRet < elements.size()){
			String buf = elements.get(iRet);
			iRet++;
			buf = elements.get(iRet);
			if(!buf.equals("(")){
				throw new SQLException("Expecting ( after range. Found: " + buf);
			}
			iRet++;
			buf = elements.get(iRet);
			try{
				this.rangeStart = Integer.parseInt(buf);
			}catch(Exception ex){
				throw new SQLException("Failed to parese range start: " + ex.getMessage());
			}
			iRet++;
			buf = elements.get(iRet);
			if(!buf.equals(",")){
				throw new SQLException("Expecting , after range start. Found: " + buf);
			}
			iRet++;
			buf = elements.get(iRet);
			try{
				this.rangeEnd = Integer.parseInt(buf);
			}catch(Exception ex){
				throw new SQLException("Failed to parese range end: " + ex.getMessage());
			}
			iRet++;
			buf = elements.get(iRet);
			if(!buf.equals(")")){
				throw new SQLException("Expecting ) after range end. Found: " + buf);
			}
//			System.out.println("buf 6: " + buf);
//		}
		iRet++;
		return iRet;
	}
	
	private int parserOrderBy(int i) throws SQLException{
		int iRet = i;
		while(iRet < elements.size()){
			String strSort[] = new String[2];
			String part1 = elements.get(iRet).toUpperCase();
			String part2 = elements.get(iRet + 1).toUpperCase();
			String part2org = part2;

			if(!part2.equals(",") && !part2.equals(SQLParser.ORDER_ASC) && !part2.equals(SQLParser.ORDER_DESC) && !part2.equals(";")){
				throw new SQLException("Expecting ',', ';', 'ASC' or 'DESC'. Found: " + part2);
			}
			
			if(part2.equals(",") || part2.equals(";")){
				iRet++;
				part2 = SQLParser.ORDER_DESC;
			}else{
				iRet++;
				iRet++;
			}
			
			strSort[0] = part1;
			strSort[1] = part2;
			this.orderBy.add(strSort);

			if(part2org.equals(";")){
				return elements.size() + 1;
			}
			
			iRet++;
		}
		return iRet;
	}
	
	public List<String> getSelectColumns(){
		return this.columns;
	}

	public List<String> getSubQueries(){
		return this.subqueries;
	}
	
	public String getTable(){
		return this.table;
	}

	public String getWherePart() {
		return wherePart;
	}

	public List<String> getWhereSubQueries() {
		return this.whereSubqueries;
	}
	
	public List<String[]> getOrderBy() {
		return this.orderBy;
	}

	public String getRowId(){
		return this.rowid;
	}
	
}
